using System;
using System.Net.Sockets;

namespace ServerNET.Server
{
    using System.Net;
    using System.IO;
    using System.Collections.Generic;
    using System.Text;
    using System.Security.Cryptography;
    using System.Text.RegularExpressions;
    using ServerNET.Server;
    using System.Threading;

    public delegate void ClientConnectedEventHandler(CClient Connection, EventArgs e);
    public enum ServerLogLevel { Nothing, Subtle, Verbose };
    public class CServer
    {
        private const string STR_SecWebSocketVersion = "Sec-WebSocket-Version";
        private const string STR_ServerName = "localhost";

        public event ClientConnectedEventHandler ClientConnected;
        public Socket Acceptor;
        public CClient[] ClientList;
        public int ClientCount;
        public CChannel[] ChannelList;
        public CTable[] TableList;
        public int CCU;
        public bool Active;
        public bool Debug = true;

        // Constructor
        public CServer()
        {
            CCU = 0;
            Active = true;
        }

        public void Start()
        {
            CLog.LogSystem("SERVER_START|" + CGlobal.Config.Version);

            Thread thread = new Thread(new ThreadStart(Loop));
            thread.Start();

            Listen();
        }

        // Loop
        public void Loop()
        {
            while (Active)
            {
                OnTimer();
                Thread.Sleep(1000);
            }
        }

        // Listen
        public void Listen()
        {
            try
            {
                Acceptor = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                IPAddress IpAddress = IPAddress.Parse(CGlobal.Config.Server);
                IPEndPoint IpLocal = new IPEndPoint(IpAddress, CGlobal.Config.Port);
                Acceptor.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                Acceptor.Bind(IpLocal);
                Acceptor.Listen(100);
                CLog.Trace("Server started on " + Acceptor.LocalEndPoint);

                // Begin listen connection
                Acceptor.BeginAccept(new AsyncCallback(OnConnect), null);
            }
            catch (SocketException e)
            {
                CLog.LogError("StartServer SocketException: " + e.ToString());
            }
        }

        // On Connect
        public void OnConnect(IAsyncResult Asyn)
        {
            try
            {
                // Get new slot
                int Id = GetSlot();
                if (Id == -1)
                {
                    // Full connection
                    return;
                }

                // Create new Client
                Socket ClientSocket = Acceptor.EndAccept(Asyn);
                CClient Client = ClientList[Id];
                Client.Create(Id, ClientSocket);
                CLog.Trace("Slot " + Id + " connected");

                // Handshake
                if (CGlobal.Config.EnableHandShake)
                {
                    Client.HandShake();
                }

                // Keep track of the new connection
                Client.Disconnected += OnDisconnect;
                Client.ReadData();

                // Invoke the connection event
                if (ClientConnected != null)
                    ClientConnected(Client, EventArgs.Empty);

                // Receive packet
                Client.DataReceived += OnReceive;

                // Listen more connection
                Acceptor.BeginAccept(new AsyncCallback(OnConnect), null);
            }
            catch (ObjectDisposedException e)
            {
                CLog.LogError("OnConnect ObjectDisposedException: " + e.ToString());
            }
            catch (SocketException e)
            {
                CLog.LogError("OnConnect SocketException: " + e.ToString());
            }
        }

        // Handshake
        private void Handshake(Socket ClientSocket)
        {
            String webSocketsVersion = "";

            using (NetworkStream stream = new NetworkStream(ClientSocket))
            using (StreamReader reader = new StreamReader(stream))
            using (StreamWriter writer = new StreamWriter(stream))
            {
                //read handshake from client (no need to actually read it, we know its there):
                CLog.Trace("Reading client handshake:" + ServerLogLevel.Verbose);
                Dictionary<string, string> headers = new Dictionary<string, string>();
                string line = string.Empty;
                while ((line = ReadLine(stream)) != string.Empty)
                {
                    Console.WriteLine("\t{0}", line);
                    String[] tokens = line.Split(new char[] { ':' }, 2);
                    if (!string.IsNullOrEmpty(line) && tokens.Length > 1)
                    {
                        headers[tokens[0]] = tokens[1].Trim();
                    }
                }
                // check websockets version
                // send handshake to the client according to the websockets version requested
                if (headers.ContainsKey(STR_SecWebSocketVersion))
                {
                    webSocketsVersion = headers[STR_SecWebSocketVersion];
                }
                {
                    switch (webSocketsVersion)
                    {
                        case "":
                            writer.Write(HandShake00(stream, headers, String.Format("ws://{0}:{1}/websession", STR_ServerName, CGlobal.Config.Port), "sample"));
                            break;
                        case "06":
                            writer.Write(HandShake06(ComputeWSAccept(headers)));
                            break;
                        case "13":
                            writer.Write(HandShake13("sample", ComputeWSAccept(headers)));
                            break;
                        default:
                            CLog.Trace("protocol handling error!");
                            break;
                    }
                }
            }
        }

        private static String ComputeWSAccept(Dictionary<string, string> headers)
        {
            string key = headers["Sec-WebSocket-Key"];
            byte[] decodedKey = Convert.FromBase64String(key);
            SHA1 sha1 = new SHA1CryptoServiceProvider();
            string wsAccept = Convert.ToBase64String(sha1.ComputeHash(Encoding.UTF8.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")));
            return wsAccept;
        }

        private static String HandShake13(string protocol, string wsAccept)
        {
            String response = "HTTP/1.1 101 Switching Protocols" + Environment.NewLine +
                              "Upgrade: websocket" + Environment.NewLine +
                              "Connection: Upgrade" + Environment.NewLine +
                              "Sec-WebSocket-Accept: " + wsAccept + Environment.NewLine +
                              "Sec-WebSocket-Protocol: " + protocol + Environment.NewLine +
                              Environment.NewLine;
            return response;
        }

        private static String HandShake06(string wsAccept)
        {
            String response = "HTTP/1.1 101 Switching Protocols" + Environment.NewLine +
                              "Upgrade: websocket" + Environment.NewLine +
                              "Connection: Upgrade" + Environment.NewLine +
                              "Sec-WebSocket-Accept: " + wsAccept + Environment.NewLine +
                              Environment.NewLine;
            return response;
        }

        private static String HandShake00(NetworkStream stream, Dictionary<string, string> headers, string urlLocation, string protocol)
        {
            List<byte> result = new List<byte>();
            byte[] key = new byte[8];
            stream.Read(key, 0, key.Length);

            string key1 = headers["Sec-WebSocket-Key1"];
            string key2 = headers["Sec-WebSocket-Key2"];

            long numbersKey1 = Convert.ToInt64(string.Join(null, Regex.Split(key1, "[^\\d]")));
            long numbersKey2 = Convert.ToInt64(string.Join(null, Regex.Split(key2, "[^\\d]")));
            int numberSpaces1 = CountSpaces(key1);
            int numberSpaces2 = CountSpaces(key2);

            int part1 = (int)(numbersKey1 / numberSpaces1);
            int part2 = (int)(numbersKey2 / numberSpaces2);

            result.AddRange(GetBigEndianBytes(part1));
            result.AddRange(GetBigEndianBytes(part2));
            result.AddRange(key);

            Byte[] handshake = null;
            using (MD5 md5 = MD5.Create())
            {
                handshake = md5.ComputeHash(result.ToArray());
            }
            String response = "HTTP/1.1 101 WebSocket Protocol Handshake" + Environment.NewLine +
                               "Upgrade: WebSocket" + Environment.NewLine +
                               "Connection: Upgrade" + Environment.NewLine +
                               "Sec-WebSocket-Origin: " + headers["Origin"] + Environment.NewLine +
                               "Sec-WebSocket-Location: " + urlLocation + Environment.NewLine +
                               "Sec-WebSocket-Protocol: " + protocol + Environment.NewLine +
                               Encoding.UTF8.GetString(handshake) + Environment.NewLine +
                               Environment.NewLine;
            return response;
        }

        static int CountSpaces(string key)
        {
            return key.Length - key.Replace(" ", string.Empty).Length;
        }

        static byte[] GetBigEndianBytes(int value)
        {
            int bytes = 4;
            byte[] buffer = new byte[bytes];
            int num = bytes - 1;
            for (int i = 0; i < bytes; i++)
            {
                buffer[num - i] = (byte)(value & 0xffL);
                value = value >> 8;
            }
            return buffer;
        }

        static string ReadLine(Stream stream)
        {
            StringBuilder sb = new StringBuilder();
            List<byte> buffer = new List<byte>();
            while (true)
            {
                buffer.Add((byte)stream.ReadByte());
                String line = Encoding.ASCII.GetString(buffer.ToArray());
                if (line.EndsWith(Environment.NewLine))
                {
                    return line.Substring(0, line.Length - 2);
                }
            }
        }

        // On Receive
        void OnReceive(CClient Connection, CPacket Packet)
        {
            try
            {
                CGlobal.PacketHandler.OnReceive(Connection, Packet);
            }
            catch (Exception e)
            {
                CLog.LogError("Error OnReceive: " + e.ToString());
            }
        }

        // On Disconnect
        public void OnDisconnect(CClient Connection, EventArgs e)
        {
            try
            {
                CLog.Trace("<" + Connection.UserName + "> disconnected");
                Connection.DataReceived -= OnReceive;
                Connection.Disconnected -= OnDisconnect;
                Connection.UserName = "";
                Connection.Close();
                ClientLogout(Connection);
            }
            catch (Exception ex)
            {
                CLog.LogError("Error OnDisconnect: " + ex.ToString());
            }
        }

        // Get Slot
        public int GetSlot()
        {
            for (int i = 0; i < CGlobal.Config.MaxClient; i++)
            {
                if (!ClientList[i].IsActive())
                {
                    return i;
                }
            }

            return -1;
        }

        // OnTimer
        public void OnTimer()
        {
            try
            {
                for (int i = 0; i < CGlobal.Config.MaxTable; i++)
                {
                    CTable Table = TableList[i];
                    Table.OnTimer();
                }

                for (int i = 0; i < CGlobal.Config.MaxClient; i++)
                {
                    CClient Client = ClientList[i];
                    if (Client.IsActive() && Client.IsTimeout() && !Client.IsBot)
                    {
                        OnDisconnect(Client, null);
                    }
                }
            }
            catch (Exception ex)
            {
                CLog.LogError("OnTimer: " + ex.ToString());
            }
        }

        // Login
        public void ClientLogin(CClient Connection, CUserInfo UserInfo)
        {
            CClient Client = ClientList[Connection.ClientId];
            if (Client.IsLogin())
            {
                return;
            }

            Client.Login(UserInfo);
            ClientCount++;
            CCU++;
            CLog.LogCCU(CCU);
        }

        // Logout
        public void ClientLogout(CClient Connection)
        {
            CClient Client = GetClient(Connection.ClientId);
            if (Client == null)
            {
                return;
            }
            if (!Client.IsLogin())
            {
                return;
            }

            try
            {
                // Client is in Table
                if (Client.IsInTable())
                {
                    // Send Notify
                    Client.Table.SendClientLeft(Client, true);

                    // Remove Client
                    Client.Table.RemoveClient(Client);
                    Client.LeftTable();
                }
            }
            catch(Exception e)
            {
                CLog.LogError("Error ClientLogout: " + e.ToString());
            }

            // Channel
            if (Client.IsInChannel())
            {
                Client.Channel.ClientLeft();
                Client.LeftChannel();
            }

            // Logout
            Client.Logout();
            ClientCount--;
            if (ClientCount < 0) ClientCount = 0;
            CCU--;
            if (CCU < 0) CCU = 0;
            CLog.LogCCU(CCU);
        }

        // Get Channel
        public CChannel GetChannel(int ChannelId)
        {
            if (ChannelId >= 0 && ChannelId < CGlobal.Config.MaxRoom)
            {
                return ChannelList[ChannelId];
            }

            return null;
        }

        // Get Table
        public CTable GetTable(int TableId)
        {
            if (TableId >= 0 && TableId < CGlobal.Config.MaxTable)
            {
                return TableList[TableId];
            }

            return TableList[TableId];
        }

        // Get Client
        public CClient GetClient(int ClientId)
        {
            if (ClientId >= 0 && ClientId < CGlobal.Config.MaxClient)
            {
                return ClientList[ClientId];
            }

            return null;
        }

        // Get Client By Account Id
        public CClient GetClientByAccountId(int AccountId)
        {
            for (int i = 0; i < CGlobal.Config.MaxClient; i++)
            {
                CClient Client = ClientList[i];
                if (Client.IsLogin() && Client.AccountId == AccountId)
                {
                    return Client;
                }
            }

            return null;
        }

        // Get Client By UserName
        public CClient GetClientByUserName(string UserName)
        {
            for (int i = 0; i < CGlobal.Config.MaxClient; i++)
            {
                CClient Client = ClientList[i];
                if (Client.IsLogin() && Client.UserName.Equals(UserName))
                {
                    return Client;
                }
            }

            return null;
        }

        // Find Avaiable Table
        public CTable FindAvailTable(CChannel Channel, double LimitGold)
        {
            for (int i = 0; i < CGlobal.Config.MaxTable; i++)
            {
                CTable Table = GetTable(i);
                if (Table != null && Table.Channel == Channel &&
                    Table.GoldBet <= LimitGold &&
                    Table.GameStage == CCommon.GS_WAITING_ROOM && Table.GetNumPlayer() > 0)
                {
                    return Table;
                }
            }

            for (int i = 0; i < CGlobal.Config.MaxTable; i++)
            {
                CTable Table = GetTable(i);
                if (Table != null && Table.Channel == Channel &&
                    Table.GoldBet <= LimitGold &&
                    Table.GameStage == CCommon.GS_WAITING_ROOM && Table.GetNumPlayer() == 0)
                {
                    return Table;
                }
            }

            return null;
        }

        // Add Bot
        public CClient AddBot(string UserName)
        {
            int SlotId = GetSlot();
            if (SlotId == -1)
            {
                // Full Slot
                return null;
            }

            double Gold = CCommon.RandomNumber(0, 70000) + 30000;
            int AvatarId = CCommon.RandomNumber(0, 9);

            CClient Bot = ClientList[SlotId];
            Bot.ClientId = SlotId;
            Bot.SetGold(Gold);
            Bot.AccountId = -1;
            Bot.AvatarId = AvatarId;
            Bot.Exp = 0;
            Bot.Level = 1;
            Bot.ClientStage = CCommon.CS_IN_GAME;
            Bot.UserName = UserName;
            Bot.Channel = null;
            Bot.Table = null;
            Bot.IsViewer = false;
            Bot.IsBot = true;
            ClientCount++;
            CLog.Trace("### New bot - Id = " + Bot.ClientId + " - NumClient = " + ClientCount);

            return Bot;
        }

        // Remove Bot
        public void RemoveBot(CClient Bot)
        {
            Bot.Table.SendClientLeft(Bot, true);
            Bot.Table.RemoveClient(Bot);
            Bot.LeftTable();
            Bot.Channel.ClientLeft();
            Bot.LeftChannel();
            Bot.Logout();
            ClientCount--;
            if (ClientCount < 0) ClientCount = 0;
        }

        // Broad Cast Message
        public void BroadCast(CPacket Packet)
        {
            for (int i = 0; i < CGlobal.Config.MaxClient; i++)
            {
                CClient Client = ClientList[i];
                if (Client.IsLogin() && !Client.IsBot)
                {
                    Client.Send(Packet);
                }
            }
        }

        // Lấy top người chơi VIP đang online
        public void GetTopVIP(ref List<CUserInfo> UserList)
        {
            UserList.Clear();

            for (int i = 0; i < CGlobal.Config.MaxClient; i++)
            {
                CClient Client = ClientList[i];
                if (Client.IsLogin() && !Client.IsBot)
                {
                    CUserInfo User = new CUserInfo();
                    User.AccountId = Client.AccountId;
                    User.AvatarId = Client.AvatarId;
                    User.Gold = Client.GetGold();
                    User.UserName = Client.UserName;
                    User.Status = Client.Status;
                    User.UserId = Client.ClientId;
                    User.Online = 1;
                    User.GameId = (Client.Table != null) ? Client.Table.GameId : -1;
                    User.ChannelId = (Client.Channel != null) ? Client.Channel.ChannelId : -1;
                    User.AppId = (Client.Channel != null) ? Client.Channel.AppId : -1;

                    // Đã có danh sách
                    if (UserList.Count > 0)
                    {
                        int Index = -1;
                        for (int j = 0; j < UserList.Count; j++)
                        {
                            // Tìm thằng giàu hơn
                            if (User.Gold > UserList[j].Gold)
                            {
                                Index = j;
                                break;
                            }
                        }

                        // Thằng này nghèo hơn cả bọn
                        if (Index == -1)
                        {
                            if (UserList.Count < 7)
                            {
                                UserList.Add(User);
                            }
                        }
                        // Cho thằng này vào top
                        else
                        {
                            UserList.Insert(Index, User);
                            if (UserList.Count > 7)
                            {
                                UserList.RemoveAt(UserList.Count - 1);
                            }
                        }
                    }
                    // Chưa có trong danh sách
                    else
                    {
                        UserList.Add(User);
                    }
                }            
            }
        }

        // Lấy top cao thủ game
        public void GetTopGame(int AppId, ref List<CUserInfo> UserList)
        {
            UserList.Clear();

            for (int i = 0; i < CGlobal.Config.MaxClient; i++)
            {
                CClient Client = ClientList[i];
                if (Client.IsLogin() && !Client.IsBot)
                {
                    CUserGame UserGame = Client.GetUserGame(AppId);
                    if (UserGame == null)
                    {
                        continue;
                    }

                    CUserInfo User = new CUserInfo();
                    User.AccountId = Client.AccountId;
                    User.AvatarId = Client.AvatarId;
                    User.Gold = Client.GetGold();
                    User.UserName = Client.UserName;
                    User.Status = Client.Status;
                    User.UserId = Client.ClientId;
                    User.Exp = UserGame.Exp;
                    User.Level = UserGame.Level;
                    User.UserId = Client.ClientId;
                    User.Online = 1;
                    User.GameId = (Client.Table != null) ? Client.Table.GameId : -1;
                    User.ChannelId = (Client.Channel != null) ? Client.Channel.ChannelId : -1;
                    User.AppId = (Client.Channel != null) ? Client.Channel.AppId : -1;

                    // Đã có danh sách
                    if (UserList.Count > 0)
                    {
                        int Index = -1;
                        for (int j = 0; j < UserList.Count; j++)
                        {
                            // Tìm thằng nhiều exp hơn
                            if (User.Exp > UserList[j].Exp)
                            {
                                Index = j;
                                break;
                            }
                        }

                        // Thằng này ít exp hơn cả bọn
                        if (Index == -1)
                        {
                            if (UserList.Count < 7)
                            {
                                UserList.Add(User);
                            }
                        }
                        // Cho thằng này vào top
                        else
                        {
                            UserList.Insert(Index, User);
                            if (UserList.Count > 7)
                            {
                                UserList.RemoveAt(UserList.Count - 1);
                            }
                        }
                    }
                    // Chưa có trong danh sách
                    else
                    {
                        UserList.Add(User);
                    }
                }
            }
        }
    }
}
